package com.heaton.videocar.core

import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.os.Handler
import android.text.TextUtils
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import cn.com.heaton.blelibrary.ble.Ble
import cn.com.heaton.blelibrary.ble.BleLog
import cn.com.heaton.blelibrary.ble.callback.BleConnectCallback
import cn.com.heaton.blelibrary.ble.callback.BleNotifyCallback
import cn.com.heaton.blelibrary.ble.callback.BleScanCallback
import cn.com.heaton.blelibrary.ble.callback.BleStatusCallback
import cn.com.heaton.blelibrary.ble.model.BleDevice
import cn.com.heaton.blelibrary.ble.model.ScanRecord
import cn.com.heaton.blelibrary.ble.utils.ByteUtils
import cn.com.heaton.blelibrary.ble.utils.UuidUtils
import com.heaton.baselib.utils.LogUtils
import com.heaton.baselib.utils.ToastUtil
import com.heaton.videocar.model.Device
import com.heaton.videocar.ui
import org.jetbrains.annotations.NotNull
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList

class BleManager: LifecycleObserver {

    private lateinit var ble: Ble<BleDevice>
    private var bleCallbacks = CopyOnWriteArrayList<BleCallback>()
    private val callbackMap = mutableMapOf<LifecycleOwner, BleCallback>()
    private var isReady = false
    private val handler = Handler()
    val currentDevice = Device()

    companion object {
        const val TAG: String = "BleManager"

        private var instance: BleManager? = null
            get() {
                if (field == null) {
                    field = BleManager()
                }
                return field
            }

        @Synchronized
        @JvmStatic
        fun get(): BleManager {
            return instance!!
        }
    }

    fun init(context: Context) {
        ble = Ble.options()
                .setLogBleEnable(true) //Set whether to output and print Bluetooth log
                .setThrowBleException(true) //Set whether to throw a Bluetooth exception
                .setLogTAG("AndroidBLE") //Set global Bluetooth operation log TAG
                .setAutoConnect(true)
                .setIgnoreRepeat(false) //Set whether to filter the scanned devices
                .setConnectFailedRetryCount(3) //When the connection is abnormal (such as Bluetooth protocol stack error), the number of reconnections
                .setConnectTimeout(6 * 1000.toLong()) //Set the connection timeout period
                .setScanPeriod(15 * 1000.toLong()) //Set scan duration
                .setUuidService(UUID.fromString(UuidUtils.uuid16To128("ae00")))
                .setUuidWriteCha(UUID.fromString(UuidUtils.uuid16To128("ae01")))
                .create(context, object : Ble.InitCallback {
                    override fun success() {
                        BleLog.e("MainApplication", "init success")
                    }

                    override fun failed(failedCode: Int) {
                        BleLog.e("MainApplication", "init fail：$failedCode")
                    }
                })
        ble.setBleStatusCallback(bleStatusCallback())
        payload[0] = 0xA8.toByte()
    }

    fun getConnectedDevice(): BleDevice? {
        val connetedDevices = Ble.getInstance<BleDevice>().connectedDevices
        if (connetedDevices.isNotEmpty()) {
            return connetedDevices[0]
        }
        return null
    }

    private fun bleStatusCallback(): BleStatusCallback? {
        return BleStatusCallback { isOn ->
            if (isOn) {
                scan()
            }
        }
    }

    fun scan() {
        ble.startScan(bleScanCallback)
    }

    fun stopScan() {
        ble.stopScan()
    }

    private var bleScanCallback = object : BleScanCallback<BleDevice>() {

        override fun onStart() {
            super.onStart()
            LogUtils.logi("$TAG>>>[onStart]: ")
            bleCallbacks.forEach {
                it.onScanStart()
            }
        }

        override fun onStop() {
            super.onStop()
            LogUtils.logi("$TAG>>>[onStop]: ")
            bleCallbacks.forEach {
                it.onScanEnd()
            }
        }

        override fun onLeScan(device: BleDevice, rssi: Int, scanRecord: ByteArray?) {
            if (TextUtils.isEmpty(device.bleName))return
            bleCallbacks.forEach {
                it.onScan(device, rssi, scanRecord)
            }
        }

        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            if (errorCode == -1) {
                ble.turnOnBlueToothNo()
            }
        }
    }

    fun connect(device: BleDevice?) {
        ble.connect(device, connectCallback)
    }

    fun connect(address: String){
        ble.connect(address, connectCallback)
    }

    private var connectCallback = object : BleConnectCallback<BleDevice>() {
        override fun onConnectionChanged(device: BleDevice) {
            bleCallbacks.forEach {
                it.onConnectionChanged(device)
            }
            if (device.isDisconnected) {
                isReady = false
            }
        }

        override fun onConnectException(device: BleDevice?, errorCode: Int) {
            super.onConnectException(device, errorCode)
            LogUtils.logi("$TAG>>>[onConnectException]: ")
            toast("连接异常")
            bleCallbacks.forEach {
                it.onConnectionException(device, errorCode)
            }
        }

        override fun onConnectTimeOut(device: BleDevice?) {
            super.onConnectTimeOut(device)
            LogUtils.logi("$TAG>>>[onConnectTimeOut]: ")
//            toast("连接超时")
            bleCallbacks.forEach {
                it.onConnectionTimeout(device)
            }
        }

        override fun onReady(device: BleDevice?) {
            super.onReady(device)
            toast("连接成功")
            notify(device)
            bleCallbacks.forEach {
                it.onReady(device)
            }
            device?.bleName?.let {
                currentDevice.name = it
            }
            handler.postDelayed({
                isReady = true
                //读取硬件版本号
                getDeviceVersion(null)
            }, 1000)

        }
    }

    fun toast(msg: String){
        ToastUtil.show(msg)
    }

    fun disconnect() {
        val devices = Ble.getInstance<BleDevice>().connectedDevices
        if (devices != null && devices.isNotEmpty()) {
            ble.disconnect(devices[0])
        }
    }

    fun notify(device: BleDevice?) {
        ble.enableNotify(device, true, bleNotifyCallback)
    }

    private var bleNotifyCallback = object : BleNotifyCallback<BleDevice>() {
        override fun onChanged(device: BleDevice?, characteristic: BluetoothGattCharacteristic?) {
            val data = characteristic?.value
            data?.let {
                BleLog.d("${TAG}receive mcu data>>>>>onChanged:", ByteUtils.toHexString(it))
                ui {
                    bleCallbacks.forEach { callback ->
                        callback.onChanged(device, it)
                    }
                    callback?.onCallback(data)
                    callback = null
                }
                if (data[1] == 0x52.toByte()){//版本号
                    var v1 = data[2].toInt()
                    var v2 = data[3].toInt()
                    var v3 = data[4].toInt()
                    val version = "$v1.$v2.$v3"
                    currentDevice.version = version
                }
            }
        }
    }

    private fun sendInner(data: ByteArray){
        val connectedDevice = getConnectedDevice()
        if (connectedDevice != null){
            LogUtils.loge("${TAG}----发送数据：${ByteUtils.toHexString(data)}")
            ble.write(connectedDevice, data, null)
        }
    }

    //非activity/fragment使用  必须remove
    fun setBleCallback(callback: BleCallback) {
        if (!bleCallbacks.contains(callback)) {
            bleCallbacks.add(callback)
        }
    }

    fun removeBleCallback(callback: BleCallback?) {
        if (bleCallbacks.contains(callback)) {
            bleCallbacks.remove(callback)
            LogUtils.logi("$TAG>>>[removeBleCallback]: ")
        }
    }

    //activity/fragment使用  无需手动remove
    fun setBleCallback(lifecycleOwner: LifecycleOwner, callback: BleCallback) {
        if (!bleCallbacks.contains(callback)) {
            bleCallbacks.add(callback)
            callbackMap[lifecycleOwner] = callback
            lifecycleOwner.lifecycle.addObserver(this)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy(@NotNull lifecycleOwner: LifecycleOwner){
        if (callbackMap.contains(lifecycleOwner)){
            val bleCallback = callbackMap.remove(lifecycleOwner)
            bleCallbacks.remove(bleCallback)
            lifecycleOwner.lifecycle.removeObserver(this)
            LogUtils.logi("$TAG>>>[onDestroy]: ")
        }
    }

    abstract class BleCallback {
        open fun onScanStart() {}
        open fun onScanEnd() {}
        open fun onScan(device: BleDevice, rssi: Int, scanRecord: ByteArray?) {}
        open fun onParsedData(device: BleDevice, scanRecord: ScanRecord?) {}
        open fun onConnectionChanged(device: BleDevice) {}
        open fun onConnectionException(device: BleDevice?, errorCode: Int) {}
        open fun onConnectionTimeout(device: BleDevice?) {}
        open fun onChanged(device: BleDevice?, data: ByteArray) {}
        open fun onReady(device: BleDevice?) {}
    }

    private fun crc(data: ByteArray): Short{
        var crc = 0
        data.forEach {
            val b = it.toInt()
            crc += (b and 0xff)
        }
        return crc.toShort()

    }

    interface Callback {
        fun onCallback(data: ByteArray)
    }

    private var callback: Callback ?= null
    private val payload = ByteArray(8)

    /**
     * 获取设备运行状态
     */
    fun getDeviceState(callback: Callback){
        this.callback = callback
        payload[1] = 0x01
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    /**
     * 获取版本号
     */
    fun getDeviceVersion(callback: Callback?){
        this.callback = callback
        payload[1] = 0x02
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    /**
     * 获取设备电量
     */
    fun getDeviceBattery(callback: Callback?=null){
        this.callback = callback
        payload[1] = 0x03
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    /**
     * @param active 是否主动
     */
    fun coolMode(active: Boolean, callback: Callback?){
        this.callback = callback
        payload[1] = 0x04
        payload[2] = if (active) 0x10 else 0x11
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    /**
     * @param active 是否主动
     */
    fun hotMode(active: Boolean, callback: Callback?){
        this.callback = callback
        payload[1] = 0x05
        payload[2] = if (active) 0x10 else 0x11
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    /**
     * @param level  Payload：00-04为降温0-4挡；0X10-0X14为加热0-4挡，
     * 控制模式
     */
    fun controlMode(level: Int, callback: Callback?){
        this.callback = callback
        payload[1] = 0x06
        payload[2] = level.toByte()
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    /**
     * @param level  Payload：00-04为降温0-4挡；0X10-0X14为加热0-4挡，
     * 风速调节
     */
    fun coolSpeed(level: Int, callback: Callback){
        this.callback = callback
        payload[1] = 0x08
        payload[2] = level.toByte()
        var crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }

    fun power(shutdown: Boolean){
        this.callback = callback
        payload[1] = 0x07
        payload[2] = if (shutdown) 0x00 else 0x01
        val crc = crc(payload.copyOfRange(0, 6))
        val bytes = ByteUtils.getBytes(crc)
        payload[6] = bytes[0]
        payload[7] = bytes[1]
        sendInner(payload)
    }


}